package tiltmaze.view;

import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import tiltmaze.Maze;
import tiltmaze.generator.MazeGenerator;
import tiltmaze.loader.MazeLoader;
import tiltmaze.solver.MazeMotionGraph;
import tiltmaze.solver.MazeSolver;
import tiltmaze.solver.MazeStateGraph;
import tiltmaze.solver.MazeStatesAnalyzer;

/**
 * Main GUI Jrame and controller.
 * @author Juraj Nižnan
 */
public class GeneratorFrame extends JFrame {

    private HistoryQueue history = new HistoryQueue();
    private MazeCanvas canvas;
    private Container pane;
    private JMenu menuFile;
    private JMenu menuEdit;
    private JMenuItem menuEditUndo;
    private JMenuItem menuEditRedo;
    private JMenu menuTools;
    private JCheckBoxMenuItem menuToolsSettings;
    private JMenu menuHelp;
    private JTextArea textArea;
    private JButton btnSolve;
    private JButton btnAnalyze;
    private JButton btnStep;
    private JButton btnStep10;
    private JButton btnStepEnd;
    private JButton btnRandomize;
    private JDialog dialogAbout;
    private SettingsManager settings = new SettingsManager();

    public GeneratorFrame() {
        super("Tilt Maze Generator");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        pane = getContentPane();
        pane.setLayout(new BoxLayout(pane, BoxLayout.LINE_AXIS));

        //MAZE CANVAS:
        canvas = new MazeCanvas(new Maze(7, 7));
        history.add(new Maze(7, 7));
        pane.add(canvas);

        //MENU BAR:
        JMenuBar menuBar = new JMenuBar();

        menuFile = new JMenu("File");
        menuFile.setMnemonic(KeyEvent.VK_F);
        menuBar.add(menuFile);
        JMenuItem menuFileNew = new JMenuItem("New");
        menuFileNew.addActionListener(btnNewListener);
        menuFileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
        menuFile.add(menuFileNew);
        JMenuItem menuFileOpen = new JMenuItem("Open");
        menuFileOpen.addActionListener(btnOpenListener);
        menuFileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
        menuFile.add(menuFileOpen);
        JMenuItem menuFileSaveAs = new JMenuItem("Save as...");
        menuFileSaveAs.addActionListener(btnSaveAsListener);
        menuFileSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
        menuFile.add(menuFileSaveAs);
        menuFile.addSeparator();
        JMenuItem menuFileExit = new JMenuItem("Exit");
        menuFileExit.addActionListener(btnExitListener);
        menuFileExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
        menuFile.add(menuFileExit);

        menuEdit = new JMenu("Edit");
        menuBar.add(menuEdit);
        menuEditUndo = new JMenuItem("Undo");
        menuEditUndo.addActionListener(btnUndoListener);
        menuEditUndo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
        menuEditUndo.setEnabled(false);
        menuEdit.add(menuEditUndo);
        menuEditRedo = new JMenuItem("Redo");
        menuEditRedo.addActionListener(btnRedoListener);
        menuEditRedo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
        menuEditRedo.setEnabled(false);
        menuEdit.add(menuEditRedo);

        menuTools = new JMenu("Tools");
        menuBar.add(menuTools);
        JMenuItem menuToolsView = new JMenuItem("View Maze");
        menuToolsView.addActionListener(btnViewListener);
        menuTools.add(menuToolsView);
        menuTools.addSeparator();
        JMenuItem menuToolsStateGraph = new JMenuItem("Generate state graph...");
        menuToolsStateGraph.addActionListener(btnStateGraphListener);
        menuTools.add(menuToolsStateGraph);
        JMenuItem menuToolsStateSimpleGraph = new JMenuItem("Generate simplified state graph...");
        menuToolsStateSimpleGraph.addActionListener(btnStateSimpleGraphListener);
        menuTools.add(menuToolsStateSimpleGraph);
        JMenuItem menuToolsMotionGraph = new JMenuItem("Generate motion graph...");
        menuToolsMotionGraph.addActionListener(btnMotionGraphListener);
        menuTools.add(menuToolsMotionGraph);
        menuTools.addSeparator();
        menuToolsSettings = new JCheckBoxMenuItem("Settings");
        menuToolsSettings.addActionListener(btnSettingsListener);
        menuTools.add(menuToolsSettings);

        menuHelp = new JMenu("Help");
        menuBar.add(menuHelp);
        JMenuItem menuHelpAbout = new JMenuItem("About");
        menuHelpAbout.addActionListener(btnAboutListener);
        menuHelp.add(menuHelpAbout);
        dialogAbout = new AboutBox(this);

        this.setJMenuBar(menuBar);

        //RIGHT PANEL:
        JPanel rightPanel = new JPanel();
        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.PAGE_AXIS));
        rightPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));

        btnSolve = new JButton("Solve");
        btnSolve.addActionListener(btnSolveListener);
        btnSolve.setMnemonic(KeyEvent.VK_S);
        buttonPanel.add(btnSolve);

        btnAnalyze = new JButton("Analyze");
        btnAnalyze.addActionListener(btnAnalyzeListener);
        buttonPanel.add(btnAnalyze);

        btnStep = new JButton(">");
        btnStep.addActionListener(btnStepListener);
        buttonPanel.add(btnStep);

        btnStep10 = new JButton(">>");
        btnStep10.addActionListener(btnStep10Listener);
        buttonPanel.add(btnStep10);

        btnStepEnd = new JButton(">|");
        btnStepEnd.addActionListener(btnStepEndListener);
        buttonPanel.add(btnStepEnd);

        btnRandomize = new JButton("Randomize");
        btnRandomize.addActionListener(btnRandomizeListener);
        buttonPanel.add(btnRandomize);

        rightPanel.add(buttonPanel);
        rightPanel.add(Box.createVerticalStrut(5));
        rightPanel.add(settings.panel);
        settings.panel.setVisible(false);
        rightPanel.add(Box.createVerticalStrut(10));

        textArea = new JTextArea(10, 35);
        textArea.setFont(new Font("Arial", Font.PLAIN, 14));
        rightPanel.add(textArea);

        pane.add(rightPanel);

        pack();
        setVisible(true);

    }

    /***********
     * METHODS *
     ***********/
    private void activateGUI(boolean val) {
        menuFile.setEnabled(val);
        menuEdit.setEnabled(val);
        menuTools.setEnabled(val);
        menuHelp.setEnabled(val);
        btnSolve.setEnabled(val);
        btnAnalyze.setEnabled(val);
        btnStep.setEnabled(val);
        btnStep10.setEnabled(val);
        btnStepEnd.setEnabled(val);
        btnRandomize.setEnabled(val);
    }

    private void setMaze(Maze m) {
        pane.remove(canvas);
        canvas = new MazeCanvas(m);
        pane.add(canvas, 0);
        pack();
        menuEditUndo.setEnabled(history.undoEnabled());
        menuEditRedo.setEnabled(history.redoEnabled());
    }

    private void setMazeAndSave(Maze m) {
        if (!m.equals(history.lastSaved())) {
            history.add(m);
        }
        setMaze(m);
    }
    /********************
     * ACTION LISTENERS *
     ********************/
    /* Menu FILE */
    private ActionListener btnNewListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            Object[] possibilities = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"};
            Icon icon = null;
            String s = (String) JOptionPane.showInputDialog(
                    canvas.getParent(),
                    "",
                    "Choose maze size:",
                    JOptionPane.PLAIN_MESSAGE,
                    icon,
                    possibilities,
                    "7");
            if (s != null) {
                Integer la = Integer.parseInt(s);
                setMazeAndSave(new Maze(la, la));
                setTitle("Tilt Maze Generator");
            }
        }
    };
    private ActionListener btnOpenListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            try {
                File f = new File(new File(".").getCanonicalPath());
                chooser.setCurrentDirectory(f);
            } catch (IOException ex) {
            }
            int returnVal = chooser.showOpenDialog(GeneratorFrame.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = chooser.getSelectedFile();
                setMazeAndSave(MazeLoader.importMaze(file.getAbsolutePath()));
                //System.out.println("Opening: " + file.getName() + ".");
                setTitle("Tilt Maze Generator - " + file.getName());
            } else {
                //System.out.println("Open command cancelled by user.");
            }
        }
    };
    private ActionListener btnSaveAsListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            try {
                File f = new File(new File(".").getCanonicalPath());
                chooser.setCurrentDirectory(f);
            } catch (IOException ex) {
            }
            int returnVal = chooser.showSaveDialog(GeneratorFrame.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = chooser.getSelectedFile();
                BufferedWriter out;
                try {
                    out = new BufferedWriter(new FileWriter(file));
                    out.write(canvas.getMaze().toString());
                    out.close();
                } catch (IOException ex) {
                    Logger.getLogger(GeneratorFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
                //System.out.println("Saving to: " + file.getName() + ".");
                setTitle("Tilt Maze Generator - " + file.getName());
            } else {
                //System.out.println("Save command cancelled by user.");
            }
        }
    };
    private ActionListener btnExitListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    };
    /* Menu EDIT */
    private ActionListener btnUndoListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            setMaze(history.undo());
        }
    };
    private ActionListener btnRedoListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            setMaze(history.redo());
        }
    };
    /* Menu Tools */
    private ActionListener btnViewListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFrame playFrame = new JFrame("View Maze");
            playFrame.getContentPane().add(new MazeDisplay(canvas.getMaze()));
            playFrame.pack();
            playFrame.setVisible(true);
        }
    };
    private ActionListener btnMotionGraphListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            try {
                File f = new File(new File(".").getCanonicalPath());
                chooser.setCurrentDirectory(f);
            } catch (IOException ex) {
            }
            int returnVal = chooser.showSaveDialog(GeneratorFrame.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                final File file = chooser.getSelectedFile();
                SwingWorker sw = new SwingWorker<Void, Void>() {

                    @Override
                    protected Void doInBackground() throws Exception {
                        MazeMotionGraph mg = new MazeMotionGraph(canvas.getMaze());
                        mg.writeGML(file.getAbsolutePath() + ".gml");
                        return null;
                    }
                };
                sw.execute();
            } else {
                //System.out.println("Save command cancelled by user.");
            }
        }
    };
    private ActionListener btnStateGraphListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            try {
                File f = new File(new File(".").getCanonicalPath());
                chooser.setCurrentDirectory(f);
            } catch (IOException ex) {
            }
            int returnVal = chooser.showSaveDialog(GeneratorFrame.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                final File file = chooser.getSelectedFile();
                SwingWorker sw = new SwingWorker<Void, Void>() {

                    @Override
                    protected Void doInBackground() throws Exception {
                        MazeStateGraph msg = new MazeStateGraph(canvas.getMaze());
                        msg.writeGML(file.getAbsolutePath() + ".gml");
                        return null;
                    }
                };
                sw.execute();
            } else {
                //System.out.println("Save command cancelled by user.");
            }
        }
    };
    private ActionListener btnStateSimpleGraphListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            try {
                File f = new File(new File(".").getCanonicalPath());
                chooser.setCurrentDirectory(f);
            } catch (IOException ex) {
            }
            int returnVal = chooser.showSaveDialog(GeneratorFrame.this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                final File file = chooser.getSelectedFile();
                SwingWorker sw = new SwingWorker<Void, Void>() {

                    @Override
                    protected Void doInBackground() throws Exception {
                        MazeStatesAnalyzer msa = new MazeStatesAnalyzer(canvas.getMaze());
                        msa.analyze();
                        msa.writeGML(file.getAbsolutePath() + ".gml");
                        return null;
                    }
                };
                sw.execute();
            } else {
                //System.out.println("Save command cancelled by user.");
            }
        }
    };
    private ActionListener btnSettingsListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            settings.panel.setVisible(menuToolsSettings.isSelected());
        }
    };
    /* Menu HELP */
    private ActionListener btnAboutListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            dialogAbout.pack();
            dialogAbout.setVisible(true);
        }
    };
    /* Workspace */
    private ActionListener btnSolveListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            getSolveWorker().execute();
        }
    };
    private ActionListener btnAnalyzeListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            getAnalyzeWorker().execute();
        }
    };
    private ActionListener btnStepListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            getStepWorker(1).execute();
        }
    };
    private ActionListener btnStep10Listener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            getStepWorker(10).execute();
        }
    };
    private ActionListener btnStepEndListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            getStepWorker(0).execute();
        }
    };
    private ActionListener btnRandomizeListener = new ActionListener() {

        public void actionPerformed(ActionEvent e) {
            SwingWorker sw = new SwingWorker() {

                @Override
                protected Object doInBackground() throws Exception {
                    activateGUI(false);
                    setMazeAndSave(canvas.getMaze());
                    Maze m = MazeGenerator.randomize(canvas.getMaze());
                    setMazeAndSave(m);
                    return m;
                }

                @Override
                protected void done() {
                    activateGUI(true);
                    getAnalyzeWorker().execute();
                }
            };
            sw.execute();
        }
    };

    /*****************
     * SWING WORKERS *
     *****************/
    private SwingWorker getSolveWorker() {
        return new SwingWorker<MazeSolver, Void>() {

            @Override
            protected MazeSolver doInBackground() throws Exception {
                activateGUI(false);
                MazeSolver ms = new MazeSolver(canvas.getMaze());
                ms.solve();
                return ms;
            }

            @Override
            protected void done() {
                try {
                    textArea.setText(get().getPaths() + "\n" + "Time: " + get().getTime() + " ms");
                } catch (Exception ex) {
                }
                activateGUI(true);
            }
        };
    }

    private SwingWorker getAnalyzeWorker() {
        return new SwingWorker<MazeStatesAnalyzer, Void>() {

            @Override
            protected MazeStatesAnalyzer doInBackground() throws Exception {
                activateGUI(false);
                MazeStatesAnalyzer msa = new MazeStatesAnalyzer(canvas.getMaze());
                msa.analyze();
                return msa;
            }

            @Override
            protected void done() {
                try {
                    textArea.setText(get().getAnalysis());
                } catch (Exception ex) {
                }
                activateGUI(true);
            }
        };
    }

    private SwingWorker getStepWorker(final int repeats) {
        return new SwingWorker() {

            @Override
            protected Object doInBackground() throws Exception {
                activateGUI(false);
                MazeGenerator mg = new MazeGenerator(canvas.getMaze());
                mg.setFit(settings.getFitNum());
                setMazeAndSave(canvas.getMaze());
                for (int i = 0; i <= repeats; i++) {
                    mg.step();
                    if (repeats == 0) {
                        i--;
                    }
                    if (mg.getMaze().equals(canvas.getMaze())) {
                        i = repeats;
                    } else {
                        setMaze(mg.getMaze());
                    }
                    if (i == 0) {
                        i++;
                    }
                }
                setMazeAndSave(mg.getMaze());
                return mg;
            }

            @Override
            protected void done() {
                activateGUI(true);
                getAnalyzeWorker().execute();
            }
        };
    }
}
